/*
 * Copyright (C) 2012 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#define LOG_TAG "usb"

#define DEBUG 1

#if DEBUG
#include "alog.h"
#define D LOGD
#else
#define D(...)
#endif

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <signal.h>
#include <pthread.h>
#include <errno.h>
#include <string.h>

#include "ch9.h"
#include "usbhost.h"
#include "alog.h"

static struct usb_device *current_device = NULL;
static uint8_t read_ep;
static uint8_t write_ep;

static pthread_mutex_t device_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t device_cond = PTHREAD_COND_INITIALIZER;

struct usb_interface_descriptor* usb_get_interface_ep(struct usb_device* device, int interfaceId, uint8_t* epIn, uint8_t* epOut)
{
    struct usb_descriptor_header* desc;
    struct usb_descriptor_iter iter;
    struct usb_interface_descriptor *intf = NULL;
    struct usb_endpoint_descriptor *ep1 = NULL;
    struct usb_endpoint_descriptor *ep2 = NULL;
    int index = 0;

    D("usb_get_interface_ep...");
    usb_descriptor_iter_init(device, &iter);
    D("usb_descriptor_iter_init...");
    while ((desc = usb_descriptor_iter_next(&iter)) != NULL && (!intf || !ep1 || !ep2)) {
        D("usb_descriptor_iter_next %d...", index);
        if (desc->bDescriptorType == USB_DT_INTERFACE) {
            D("bDescriptorType USB_DT_INTERFACE...");
            if(intf != NULL)
                continue;
            intf = (struct usb_interface_descriptor *)desc;
            D("Find Interface %d", intf->bInterfaceNumber);
            ep1 = NULL;
            ep2 = NULL;

            // 不是当前需要查找的设备
            if(index != interfaceId)
            {
                intf = NULL;
            }
            ++index;
        } else if (desc->bDescriptorType == USB_DT_ENDPOINT) {
            D("bDescriptorType USB_DT_ENDPOINT...");
            if(intf == NULL)
                continue;

            struct usb_endpoint_descriptor *ep = (struct usb_endpoint_descriptor *)desc;
            if(usb_endpoint_is_bulk_out(ep)){
                ep1 = ep;
                D("Find Bulk EpOutAdr %d", ep->bEndpointAddress);
            }
            else
            if(usb_endpoint_is_bulk_in(ep)){
                ep2 = ep;
                D("Find Bulk EpInAdr %d", ep->bEndpointAddress);
            }
        }
    }
    if(!intf)
    {
        LOGE("interface not found\n");
        return NULL;
    }
    if (!ep1 || !ep2) {
        LOGE("endpoints not found\n");
        return NULL;
    }
    *epIn = ep2->bEndpointAddress;
    *epOut = ep1->bEndpointAddress;
    return intf;
}

int usb_switch_to_current(int interfaceId)
{
    struct usb_interface_descriptor *intf = NULL;
    int ret = -1;
    D("usb_switch_to_current %X", current_device);
    if(!current_device)
        return ret;
    pthread_mutex_lock(&device_mutex);
    D("usb_get_interface_ep...");
    intf = usb_get_interface_ep(current_device, interfaceId, &read_ep, &write_ep);
    D("usb_get_interface_ep %X...", intf);
    if(intf != NULL)
    {
        D("usb_device_claim_interface %d...", intf->bInterfaceNumber);
        if (usb_device_claim_interface(current_device, intf->bInterfaceNumber))
        {
            LOGE("usb_device_claim_interface failed errno: %d\n", errno);
        }
        else
        {
            ret = 0;
        }
    }
    pthread_mutex_unlock(&device_mutex);
    D("usb_switch_to_current ret[%d]...", ret);
    return ret;
}

int usb_read(unsigned char *buffer, int length, unsigned int timeout) {
    int ret = -1;
    pthread_mutex_lock(&device_mutex);
    if(current_device != NULL){
        ret = usb_device_bulk_transfer(current_device, read_ep, buffer, length, timeout);
    }
    pthread_mutex_unlock(&device_mutex);
    return ret;
}

int usb_write(unsigned char *buffer, int length, unsigned int timeout) {
    int ret = -1;
    pthread_mutex_lock(&device_mutex);
    if(current_device != NULL){
        ret = usb_device_bulk_transfer(current_device, write_ep, buffer, length, timeout);
    }
    pthread_mutex_unlock(&device_mutex);
    return ret;
}

static int usb_get_access_rights(const char *devname){
    char cmdstr[256] = "su -c chmod 0666 ";
    strcat(cmdstr, devname);
    return system(cmdstr);
}

static int usb_device_added(const char *devname, void* client_data) {
    uint16_t vendorId, productId;
    D("New device: %s", devname);
    struct usb_device *device;

    if (usb_get_access_rights(devname) != 0){
        LOGE("change access fail: %s", devname);
        return 0;
    }
    device = usb_device_open(devname);
    if (!device) {
        LOGE("usb_device_open failed\n");
        return 0;
    }

    vendorId = usb_device_get_vendor_id(device);
    productId = usb_device_get_product_id(device);
    D("VID:%04X PID:%04X", vendorId, productId);
    if (!current_device && vendorId == CCID_VID/* && productId == CCID_PID*/) {
        pthread_mutex_lock(&device_mutex);
        D("Found device\n");
        current_device = device;
        pthread_cond_broadcast(&device_cond);
        pthread_mutex_unlock(&device_mutex);
        return 1;
    } else{
        usb_device_close(device);
    }
    return 0;
}

static int usb_device_removed(const char *devname, void* client_data) {
    pthread_mutex_lock(&device_mutex);
    if (current_device && !strcmp(usb_device_get_name(current_device), devname)) {
        D("current device disconnected\n");
        usb_device_close(current_device);
        current_device = NULL;
    }
    pthread_mutex_unlock(&device_mutex);
    return 0;
}

int usb_is_present(void){
    int ret = 0;

    pthread_mutex_lock(&device_mutex);
    ret = current_device != NULL;
    pthread_mutex_unlock(&device_mutex);

    return ret;
}

struct usb_device* usb_wait_for_device(int time) {
    struct usb_device* device = NULL;
    struct timeval now;
    struct timespec outtime;

    pthread_mutex_lock(&device_mutex);

    gettimeofday(&now, NULL);
    outtime.tv_sec = now.tv_sec + time;
    outtime.tv_nsec = now.tv_usec * 1000;

    while (!current_device){
        //pthread_cond_wait(&device_cond, &device_mutex);
        int ret = pthread_cond_timedwait(&device_cond, &device_mutex, &outtime);
        if(ret == ETIMEDOUT){
            break;
        }
    }
    device = current_device;
    pthread_mutex_unlock(&device_mutex);
    return device;
}


void usb_run(void *arg) {
    struct usb_host_context* context = usb_host_init();
    if (!context) {
        LOGE("usb_host_init failed");
        return;
    }
    usb_host_run(context, usb_device_added, usb_device_removed, NULL, arg);
}

static volatile int hasInited = 0;

static void* monitor_thread(void *arg) {
    struct usb_host_context* context = usb_host_init();
    if (!context) {
        LOGE("usb_host_init failed");
    }
    else{
        usb_host_run(context, usb_device_added, usb_device_removed, NULL, arg);
    }
    hasInited = 0;
    return NULL;
}

void usb_init(void* arg){
    if(current_device != NULL) {
        usb_device_close(current_device);
    }
    current_device = NULL;

    if(!hasInited){
        pthread_t th;
        D(__FUNCTION__);
        hasInited = 1;
        pthread_create(&th, NULL, monitor_thread, arg);
        usb_wait_for_device(3);
        D("usb initialized");
    }
    else{
        D("has been initialized");
    }
}

